﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ResolverDefault.cs" company="Rolosoft Ltd">
//   © Rolosoft Ltd
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

#region License

// Copyright 2013 Rolosoft Ltd
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace Rsft.Net.Dns
{
    #region Usings

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics.Contracts;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using System.Threading.Tasks;

    using Rsft.Net.Dns.Diagnostics;
    using Rsft.Net.Dns.Entities;
    using Rsft.Net.Dns.Extensions;
    using Rsft.Net.Dns.Interfaces;

    #endregion

    /// <summary>
    ///     The resolver default.
    /// </summary>
    internal sealed class ResolverDefault : ResolverBase, IResolverAdditionalMethodsNet45, IDisposable
    {
        #region Constants

        /// <summary>
        ///     The max retries exceeded message.
        /// </summary>
        private const string MaxRetriesExceededMessage = @"Maximum retries exceeded.";

        /// <summary>
        ///     The max size of UDP datagram.
        /// </summary>
        /// <remarks>
        ///     RFC1035 max. size of a UDP datagram is 512 bytes.
        /// </remarks>
        private const int MaxSizeUdpDatagram = 512;

        #endregion

        #region Static Fields

        /// <summary>
        ///     The configuration local
        /// </summary>
        /// <remarks>
        ///     Stored in static field for persistence in memory.
        /// </remarks>
        private static IConfiguration configurationLocal;

        #endregion

        #region Fields

        /// <summary>
        ///     The disposed
        /// </summary>
        private bool disposed;

        /// <summary>
        /// The reader writer lock slim configuration.
        /// </summary>
        private ReaderWriterLockSlim readerWriterLockSlimConfiguration;

        /// <summary>
        /// The reader writer lock slim unique header id.
        /// </summary>
        private ReaderWriterLockSlim readerWriterLockSlimUniqueHeaderId;

        /// <summary>
        ///     The unique header ID for.
        /// </summary>
        private int uniqueHeaderId;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ResolverDefault"/> class.
        /// </summary>
        /// <param name="recordReaderFactory">
        /// The record reader factory.
        /// </param>
        /// <param name="cacheFactory">
        /// The cache factory.
        /// </param>
        /// <param name="configuration">
        /// The configuration.
        /// </param>
        /// <param name="logger">
        /// The logger.
        /// </param>
        public ResolverDefault(
            IRecordReaderFactory recordReaderFactory, 
            ICacheFactory cacheFactory, 
            IConfiguration configuration, 
            ILogger logger)
            : base(recordReaderFactory, cacheFactory, logger)
        {
            Contract.Requires(configuration != null);
            Contract.Requires(configuration.Configuration.DnsServers != null);
            Contract.Requires(configuration.Configuration.DnsTimeout >= 0);
            Contract.Requires(configuration.Configuration.Retries > 0);
            Contract.Requires(
                configuration.Configuration.TransportType == TransportType.Tcp
                || configuration.Configuration.TransportType == TransportType.Udp);
            Contract.Requires(recordReaderFactory != null);
            Contract.Requires(cacheFactory != null);
            Contract.Requires(logger != null);

            this.readerWriterLockSlimConfiguration = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
            this.readerWriterLockSlimUniqueHeaderId = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

            this.Configuration = configuration;
        }

        /// <summary>
        ///     Finalizes an instance of the <see cref="ResolverDefault" /> class.
        /// </summary>
        ~ResolverDefault()
        {
            this.Dispose(false);
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///     Gets or sets the configuration.
        /// </summary>
        /// <value>
        ///     The configuration.
        /// </value>
        /// <remarks>
        ///     Thread safe.
        /// </remarks>
        public override IConfiguration Configuration
        {
            get
            {
                this.readerWriterLockSlimConfiguration.EnterReadLock();
                var rtn = configurationLocal;
                this.readerWriterLockSlimConfiguration.ExitReadLock();
                return rtn;
            }

            set
            {
                this.readerWriterLockSlimConfiguration.EnterWriteLock();
                configurationLocal = value;
                this.readerWriterLockSlimConfiguration.ExitWriteLock();
            }
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets or sets the unique header id.
        /// </summary>
        /// <value>
        ///     The unique header id.
        /// </value>
        /// <remarks>
        ///     Thread safe.
        /// </remarks>
        protected override int UniqueHeaderId
        {
            get
            {
                this.readerWriterLockSlimUniqueHeaderId.EnterReadLock();
                var rtn = this.uniqueHeaderId;
                this.readerWriterLockSlimUniqueHeaderId.ExitReadLock();
                return rtn;
            }

            set
            {
                this.readerWriterLockSlimUniqueHeaderId.EnterWriteLock();

                var i = value >= ushort.MaxValue ? new Random().Next(ushort.MinValue, ushort.MaxValue) : value;

                this.uniqueHeaderId = i;

                this.readerWriterLockSlimUniqueHeaderId.ExitWriteLock();
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Begins the get host addresses.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// The host name or address.
        /// </param>
        /// <param name="requestCallback">
        /// The request callback.
        /// </param>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        public override IAsyncResult BeginGetHostAddresses(
            string hostNameOrAddress, AsyncCallback requestCallback, object value)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived, 
                string.Format(CultureInfo.InvariantCulture, "BeginGetHostAddresses : {0}", hostNameOrAddress));

            return this.GetHostAddressesAsync(hostNameOrAddress, CancellationToken.None).AsApm(requestCallback, value);
        }

        /// <summary>
        /// Begins the get host by address.
        /// </summary>
        /// <param name="address">
        /// The address.
        /// </param>
        /// <param name="asyncCallback">
        /// The async callback.
        /// </param>
        /// <param name="state">
        /// The state.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        public override IAsyncResult BeginGetHostByAddress(string address, AsyncCallback asyncCallback, object state)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived, 
                string.Format(CultureInfo.InvariantCulture, "BeginGetHostByAddress : {0}", address));

            return this.GetHostAddressesAsync(address, CancellationToken.None).AsApm(asyncCallback, state);
        }

        /// <summary>
        /// Begins the get host by address.
        /// </summary>
        /// <param name="address">
        /// The address.
        /// </param>
        /// <param name="asyncCallback">
        /// The async callback.
        /// </param>
        /// <param name="state">
        /// The state.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        public override IAsyncResult BeginGetHostByAddress(IPAddress address, AsyncCallback asyncCallback, object state)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived, 
                string.Format(CultureInfo.InvariantCulture, "BeginGetHostByAddress : {0}", address));

            return this.GetHostByAddressAsync(address, CancellationToken.None).AsApm(asyncCallback, state);
        }

        /// <summary>
        /// Begins the name of the get host by.
        /// </summary>
        /// <param name="hostName">
        /// Name of the host.
        /// </param>
        /// <param name="asyncCallback">
        /// The async callback.
        /// </param>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        public override IAsyncResult BeginGetHostByName(string hostName, AsyncCallback asyncCallback, object value)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived, 
                string.Format(CultureInfo.InvariantCulture, "BeginGetHostByName : {0}", hostName));

            return this.GetHostByNameAsync(hostName, CancellationToken.None).AsApm(asyncCallback, value);
        }

        /// <summary>
        /// Begins the get host entry.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// The host name or address.
        /// </param>
        /// <param name="asyncCallback">
        /// The async callback.
        /// </param>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        public override IAsyncResult BeginGetHostEntry(
            string hostNameOrAddress, AsyncCallback asyncCallback, object value)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived, 
                string.Format(CultureInfo.InvariantCulture, "BeginGetHostEntry : {0}", hostNameOrAddress));

            return this.GetHostEntryAsync(hostNameOrAddress, CancellationToken.None).AsApm(asyncCallback, value);
        }

        /// <summary>
        /// Begins the get host entry.
        /// </summary>
        /// <param name="address">
        /// The address.
        /// </param>
        /// <param name="asyncCallback">
        /// The async callback.
        /// </param>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        public override IAsyncResult BeginGetHostEntry(IPAddress address, AsyncCallback asyncCallback, object value)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived, 
                string.Format(CultureInfo.InvariantCulture, "BeginGetHostEntry : {0}", address));

            return this.GetHostEntryAsync(address, CancellationToken.None).AsApm(asyncCallback, value);
        }

        /// <summary>
        /// Begins the get TCP request.
        /// </summary>
        /// <param name="request">
        /// The request.
        /// </param>
        /// <param name="asyncCallback">
        /// The async callback.
        /// </param>
        /// <param name="state">
        /// The state.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        public override IAsyncResult BeginGetTcpRequest(Request request, AsyncCallback asyncCallback, object state)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived, 
                string.Format(CultureInfo.InvariantCulture, "BeginGetTcpRequest : {0}", request));

            return
                this.GetTcpRequestLocalAsync(
                    request,
                    this.Configuration.Configuration.DnsServers,
                    this.Configuration.Configuration.DnsTimeout,
                    CancellationToken.None).AsApm(asyncCallback, state);
        }

        /// <summary>
        /// Begins the get UDP request.
        /// </summary>
        /// <param name="request">
        /// The request.
        /// </param>
        /// <param name="asyncCallback">
        /// The async callback.
        /// </param>
        /// <param name="state">
        /// The state.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        public override IAsyncResult BeginGetUdpRequest(Request request, AsyncCallback asyncCallback, object state)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "BeginGetUdpRequest : {0}", request));

            return this.GetUdpRequestLocalAsync(
                request,
                this.Configuration.Configuration.DnsServers,
                this.Configuration.Configuration.Retries,
                CancellationToken.None).AsApm(asyncCallback, state);
        }

        /// <summary>
        /// Begins the query.
        /// </summary>
        /// <param name="name">
        /// The name.
        /// </param>
        /// <param name="queryType">
        /// Type of the query.
        /// </param>
        /// <param name="queryClassType">
        /// Type of the query class.
        /// </param>
        /// <param name="asyncCallback">
        /// The async callback.
        /// </param>
        /// <param name="state">
        /// The state.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        public override IAsyncResult BeginQuery(
            string name, QType queryType, QClassType queryClassType, AsyncCallback asyncCallback, object state)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "BeginQuery : name={0}, queryType={1}, queryClassType={2}", name, queryType, queryClassType));

            return this.QueryAsync(name, queryType, CancellationToken.None, queryClassType).AsApm(asyncCallback, state);
        }

        /// <summary>
        /// Begins the resolve.
        /// </summary>
        /// <param name="hostName">
        /// Name of the host.
        /// </param>
        /// <param name="asyncCallback">
        /// The async callback.
        /// </param>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        public override IAsyncResult BeginResolve(string hostName, AsyncCallback asyncCallback, object value)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "BeginResolve : {0}", hostName));

            return this.ResolveAsync(hostName, CancellationToken.None).AsApm(asyncCallback, value);
        }

        /// <summary>
        ///     Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Ends the get host addresses.
        /// </summary>
        /// <param name="asyncResult">
        /// The async result.
        /// </param>
        /// <returns>
        /// The <see cref="IPAddress"/>[].
        /// </returns>
        public override IPAddress[] EndGetHostAddresses(IAsyncResult asyncResult)
        {
            var task = (Task<IPAddress[]>)asyncResult;

            var addresses = task.Result;

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "EndGetHostAddresses : Count# {0}", addresses.Length));

            return addresses;
        }

        /// <summary>
        /// Ends the get host by address.
        /// </summary>
        /// <param name="asyncResult">
        /// The async result.
        /// </param>
        /// <returns>
        /// The <see cref="IPHostEntry"/>.
        /// </returns>
        public override IPHostEntry EndGetHostByAddress(IAsyncResult asyncResult)
        {
            var result = (Task<IPHostEntry>)asyncResult;

            var hostEntry = result.Result;

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "EndGetHostByAddress : {0}", hostEntry.HostName));

            return hostEntry;
        }

        /// <summary>
        /// Ends the name of the get host by.
        /// </summary>
        /// <param name="asyncResult">
        /// The async result.
        /// </param>
        /// <returns>
        /// The <see cref="IPHostEntry"/>.
        /// </returns>
        public override IPHostEntry EndGetHostByName(IAsyncResult asyncResult)
        {
            var result = (Task<IPHostEntry>)asyncResult;

            var hostEntry = result.Result;

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "EndGetHostByName : {0}", hostEntry.HostName));

            return hostEntry;
        }

        /// <summary>
        /// Ends the get host entry.
        /// </summary>
        /// <param name="asyncResult">
        /// The async result.
        /// </param>
        /// <returns>
        /// The <see cref="IPHostEntry"/>.
        /// </returns>
        public override IPHostEntry EndGetHostEntry(IAsyncResult asyncResult)
        {
            var result = (Task<IPHostEntry>)asyncResult;

            var hostEntry = result.Result;

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "EndGetHostEntry : {0}", hostEntry.HostName));

            return hostEntry;
        }

        /// <summary>
        /// Ends the get TCP request.
        /// </summary>
        /// <param name="asyncResult">The async result.</param>
        /// <returns>
        /// The <see cref="Response" />.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">asyncResult is null.</exception>
        public override Response EndGetTcpRequest(IAsyncResult asyncResult)
        {
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            var result = (Task<Response>)asyncResult;

            var response = result.Result;

            this.Logger.Verbose(
                (int)EventId.CommandProcessed, string.Format(CultureInfo.InvariantCulture, "EndGetTcpRequest"));

            return response;
        }

        /// <summary>
        /// Ends the get UDP request.
        /// </summary>
        /// <param name="asyncResult">
        /// The async result.
        /// </param>
        /// <returns>
        /// The <see cref="Response"/>.
        /// </returns>
        public override Response EndGetUdpRequest(IAsyncResult asyncResult)
        {
            var result = (Task<Response>)asyncResult;

            var response = result.Result;

            this.Logger.Verbose(
                (int)EventId.CommandProcessed, string.Format(CultureInfo.InvariantCulture, "EndGetUdpRequest"));

            return response;
        }

        /// <summary>
        /// Ends the query.
        /// </summary>
        /// <param name="asyncResult">
        /// The async result.
        /// </param>
        /// <returns>
        /// The <see cref="Response"/>.
        /// </returns>
        public override Response EndQuery(IAsyncResult asyncResult)
        {
            var result = (Task<Response>)asyncResult;

            var response = result.Result;

            this.Logger.Verbose((int)EventId.CommandProcessed, string.Format(CultureInfo.InvariantCulture, "EndQuery"));

            return response;
        }

        /// <summary>
        /// Ends the resolve.
        /// </summary>
        /// <param name="asyncResult">
        /// The async result.
        /// </param>
        /// <returns>
        /// The <see cref="IPHostEntry"/>.
        /// </returns>
        public override IPHostEntry EndResolve(IAsyncResult asyncResult)
        {
            var result = (Task<IPHostEntry>)asyncResult;

            var hostEntry = result.Result;

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "EndResolve : {0}", hostEntry.HostName));

            return hostEntry;
        }

        /// <summary>
        /// The get host addresses async.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// The host name or address.
        /// </param>
        /// <param name="cancellationToken">
        /// The cancellation token.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<IPAddress[]> GetHostAddressesAsync(
            string hostNameOrAddress, CancellationToken cancellationToken)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "GetHostAddressesAsync : {0}", hostNameOrAddress));

            var hostEntry = await this.GetHostEntryLocalAsync(hostNameOrAddress, cancellationToken);

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "GetHostAddressesAsync : {0}", hostNameOrAddress));

            return hostEntry.AddressList;
        }

        /// <summary>
        /// The get host by address async.
        /// </summary>
        /// <param name="address">
        /// The address.
        /// </param>
        /// <param name="cancellationToken">
        /// The cancellation token.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<IPHostEntry> GetHostByAddressAsync(IPAddress address, CancellationToken cancellationToken)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "GetHostByAddressAsync : {0}", address));

            var hostEntry = await this.GetHostEntryLocalAsync(address, cancellationToken);

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "GetHostByAddressAsync : {0}", address));

            return hostEntry;
        }

        /// <summary>
        /// The get host by address async.
        /// </summary>
        /// <param name="address">
        /// The address.
        /// </param>
        /// <param name="cancellationToken">
        /// The cancellation token.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<IPHostEntry> GetHostByAddressAsync(string address, CancellationToken cancellationToken)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "GetHostByAddressAsync : {0}", address));

            var hostEntry = await this.GetHostEntryLocalAsync(address, cancellationToken);

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "GetHostByAddressAsync : {0}", address));

            return hostEntry;
        }

        /// <summary>
        /// The get host by name async.
        /// </summary>
        /// <param name="hostName">
        /// The host name.
        /// </param>
        /// <param name="cancellationToken">
        /// The cancellation token.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<IPHostEntry> GetHostByNameAsync(string hostName, CancellationToken cancellationToken)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "GetHostByNameAsync : {0}", hostName));

            var hostEntry = await this.GetHostByNameLocalAsync(hostName, cancellationToken);

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "GetHostByNameAsync : {0}", hostName));

            return hostEntry;
        }

        /// <summary>
        /// The get host entry async.
        /// </summary>
        /// <param name="address">
        /// The address.
        /// </param>
        /// <param name="cancellationToken">
        /// The cancellation token.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<IPHostEntry> GetHostEntryAsync(IPAddress address, CancellationToken cancellationToken)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "GetHostEntryAsync : {0}", address));

            var hostEntry = await this.GetHostEntryLocalAsync(address, cancellationToken);

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "GetHostEntryAsync : {0}", address));

            return hostEntry;
        }

        /// <summary>
        /// The get host entry async.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// The host name or address.
        /// </param>
        /// <param name="cancellationToken">
        /// The cancellation token.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress, CancellationToken cancellationToken)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "GetHostEntryAsync : {0}", hostNameOrAddress));

            var hostEntry = await this.GetHostEntryLocalAsync(hostNameOrAddress, cancellationToken);

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "GetHostEntryAsync : {0}", hostNameOrAddress));

            return hostEntry;
        }

        /// <summary>
        /// The query async.
        /// </summary>
        /// <param name="name">
        /// The name.
        /// </param>
        /// <param name="queryType">
        /// The query type.
        /// </param>
        /// <param name="cancellationToken">
        /// The cancellation token.
        /// </param>
        /// <param name="queryClassType">
        /// The query class type.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<Response> QueryAsync(
            string name, QType queryType, CancellationToken cancellationToken, QClassType queryClassType = QClassType.IN)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "QueryAsync : name={0}, queryType={1}, queryClassType={2}", name, queryType, queryClassType));

            var response = await this.GetQueryLocalAsync(name, queryType, queryClassType, cancellationToken);

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "QueryAsync : name={0}, queryType={1}, queryClassType={2}", name, queryType, queryClassType));

            return response;
        }

        /// <summary>
        /// The resolve async.
        /// </summary>
        /// <param name="hostName">
        /// The host name.
        /// </param>
        /// <param name="cancellationToken">
        /// The cancellation token.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<IPHostEntry> ResolveAsync(string hostName, CancellationToken cancellationToken)
        {
            this.Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "ResolveAsync : {0}", hostName));

            var hostEntry = await this.ResolveLocalAsync(hostName, cancellationToken);

            this.Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "ResolveAsync : {0}", hostName));

            return hostEntry;
        }

        #endregion

        #region Methods

        /// <summary>
        /// The TCP request.
        /// </summary>
        /// <param name="request">
        /// The request.
        /// </param>
        /// <returns>
        /// Various stuff.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// request is null.
        /// </exception>
        /// <exception cref="System.Net.Sockets.SocketException">
        /// Socket exception.
        /// </exception>
        protected override Response TcpRequest(Request request)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            for (var intAttempts = 0; intAttempts < this.Configuration.Configuration.Retries; intAttempts++)
            {
// ReSharper disable ForCanBeConvertedToForeach
                for (var intDnsServer = 0;
// ReSharper restore ForCanBeConvertedToForeach
                     intDnsServer < this.Configuration.Configuration.DnsServers.Count;
                     intDnsServer++)
                {
                    var dnsServer = this.Configuration.Configuration.DnsServers[intDnsServer];

                    using (
                        var tcpClient = new TcpClient(dnsServer.AddressFamily)
                        {
                            ReceiveTimeout = this.Configuration.Configuration.DnsTimeout,
                            SendTimeout = this.Configuration.Configuration.DnsTimeout
                        })
                    {
                        IAsyncResult result;

                        try
                        {
                            result = tcpClient.BeginConnect(dnsServer.Address, dnsServer.Port, null, null);
                        }
                        catch (SocketException exception)
                        {
                            var message = string.Format(
                                CultureInfo.InvariantCulture, ";; Connection to nameserver {0} failed.", dnsServer);

                            this.Verbose(message);

                            this.Logger.Warning(exception, (int)EventId.NetSendReceiveFail);

                            /*next try*/
                            continue;
                        }
                        finally
                        {
                            this.UniqueHeaderId++;
                        }

                        bool success = result.AsyncWaitHandle.WaitOne(this.Configuration.Configuration.DnsTimeout, true);

                        if (!success || !tcpClient.Connected)
                        {
                            var message = string.Format(
                                    CultureInfo.InvariantCulture,
                                    ";; Connection to nameserver {0} failed after timeout of {1}ms.",
                                   dnsServer,
                                   this.Configuration.Configuration.DnsTimeout);

                            this.Verbose(message);
                            this.Logger.Warning((int)EventId.NetSendReceiveFail, message);

                            /*next try*/
                            continue;
                        }

                        using (var bufferedStream = new BufferedStream(tcpClient.GetStream()))
                        {
                            var data = request.Data;

                            var bytes = data.ToArray();

                            bufferedStream.WriteByte((byte)((bytes.Length >> 8) & 0xff));
                            bufferedStream.WriteByte((byte)(bytes.Length & 0xff));
                            bufferedStream.Write(bytes, 0, bytes.Length);
                            bufferedStream.Flush();

                            var transferResponse = new Response();
                            var soa = 0;
                            var messageSize = 0;

                            while (true)
                            {
                                var intLength = bufferedStream.ReadByte() << 8 | bufferedStream.ReadByte();
                                if (intLength <= 0)
                                {
                                    /*next try*/
                                    break;
                                }

                                messageSize += intLength;

                                bytes = new byte[intLength];
                                bufferedStream.Read(bytes, 0, intLength);

                                var response = new Response(this.RecordReaderFactory, dnsServer, bytes, intLength);

                                if (response.Answers.Count == 0)
                                {
                                    throw new SocketException((int)SocketError.HostNotFound);
                                }

                                if (response.Header.ResponseCode != RCodeType.NoError)
                                {
                                    return response;
                                }

                                if (response.Questions[0].QType != QType.AXFR)
                                {
                                    this.AddToCache(response);
                                    return response;
                                }

                                // Zone transfer
                                if (transferResponse.Questions.Count == 0)
                                {
                                    foreach (Question question in response.Questions)
                                    {
                                        transferResponse.Questions.Add(question);
                                    }
                                }

                                foreach (AnswerResourceRecord answer in response.Answers)
                                {
                                    transferResponse.Answers.Add(answer);
                                }

                                foreach (AuthorityResourceRecord authority in response.Authorities)
                                {
                                    transferResponse.Authorities.Add(authority);
                                }

                                foreach (AdditionalResourceRecord additional in response.Additionals)
                                {
                                    transferResponse.Additionals.Add(additional);
                                }

                                if (response.Answers[0].RRType == RecordType.SOA)
                                {
                                    Interlocked.Increment(ref soa);
                                }

                                if (soa != 2)
                                {
                                    continue;
                                }

                                transferResponse.Header.QdCount = (ushort)transferResponse.Questions.Count;
                                transferResponse.Header.Ancount = (ushort)transferResponse.Answers.Count;
                                transferResponse.Header.Nscount = (ushort)transferResponse.Authorities.Count;
                                transferResponse.Header.Arcount = (ushort)transferResponse.Additionals.Count;
                                transferResponse.MessageSize = messageSize;
                                return transferResponse;
                            }
                        }

                        /*next try*/
                        var message1 = string.Format(
                                    CultureInfo.InvariantCulture,
                                    ";; Connection to nameserver {0} failed",
                                    dnsServer);

                        tcpClient.Close();
                        this.Verbose(message1);
                        this.Logger.Warning((int)EventId.NetSendReceiveFail, message1);

                        this.UniqueHeaderId++;
                    }
                }
            }

            var responseMaxRetriesExcceded = new Response { Error = MaxRetriesExceededMessage };
            return responseMaxRetriesExcceded;
        }

        /// <summary>
        /// The UDP request.
        /// </summary>
        /// <param name="request">
        /// The request.
        /// </param>
        /// <returns>
        /// Various stuff.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// request is null
        /// </exception>
        protected override Response UdpRequest(Request request)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            var responseMessage = new byte[MaxSizeUdpDatagram];

            for (var intAttempts = 0; intAttempts < this.Configuration.Configuration.Retries; intAttempts++)
            {
                for (var intDnsServer = 0;
                     intDnsServer < LinqForNet20.Count(this.Configuration.Configuration.DnsServers);
                     intDnsServer++)
                {
                    var dnsServer = this.Configuration.Configuration.DnsServers[intDnsServer];

                    using (
                        var socket = new Socket(dnsServer.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
                        {
                            SendTimeout
                                =
                                this
                                .Configuration
                                .Configuration
                                .DnsTimeout,
                            ReceiveTimeout
                                =
                                this
                                .Configuration
                                .Configuration
                                .DnsTimeout
                        })
                    {
                        var socketError = SocketError.Success;

                        int intReceived;

                        var array = request.Data.ToArray();

                        try
                        {
                            socket.SendTo(array, dnsServer);
                        }
                        catch (SocketException exception)
                        {
                            var message = string.Format(
                                    CultureInfo.InvariantCulture,
                                    ";; Connection to nameserver {0} failed. SocketError = {1}",
                                   dnsServer,
                                   socketError);

                            this.Verbose(message);

                            this.Logger.Warning(exception, (int)EventId.NetSendReceiveFail);

                            continue; // next try
                        }
                        finally
                        {
                            this.UniqueHeaderId++;
                        }

                        try
                        {
                            intReceived = socket.Receive(
                                responseMessage, 0, responseMessage.Length, SocketFlags.None, out socketError);
                            if (socketError != SocketError.Success)
                            {
                                throw new SocketException((int)socketError);
                            }
                        }
                        catch (SocketException exception)
                        {
                            var message = string.Format(
                                    CultureInfo.InvariantCulture,
                                    ";; Connection to nameserver {0} failed after timeout of {1}ms. SocketError = {2}",
                                   dnsServer,
                                   this.Configuration.Configuration.DnsTimeout,
                                   socketError);

                            this.Verbose(message);

                            this.Logger.Warning(exception, (int)EventId.NetSendReceiveFail);

                            continue; // next try
                        }
                        finally
                        {
                            this.UniqueHeaderId++;
                        }

                        var data = new byte[intReceived];
                        Array.Copy(responseMessage, data, intReceived);
                        var response = new Response(
                            this.RecordReaderFactory,
                            this.Configuration.Configuration.DnsServers[intDnsServer],
                            data,
                            intReceived);

                        if (response.Header.Truncation)
                        {
                            return response;
                        }

                        if (response.Answers.Count == 0)
                        {
                            throw new SocketException((int)SocketError.HostNotFound);
                        }

                        this.AddToCache(response);
                        return response;
                    }
                }
            }

            var responseMaxTriesExceeded = new Response { Error = MaxRetriesExceededMessage };
            return responseMaxTriesExceeded;
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing">
        /// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.
        /// </param>
        private void Dispose(bool disposing)
        {
            if (this.disposed)
            {
                return;
            }

            if (disposing)
            {
                // clean up managed resources
                if (this.readerWriterLockSlimConfiguration != null)
                {
                    this.readerWriterLockSlimConfiguration.Dispose();
                }

                if (this.readerWriterLockSlimUniqueHeaderId != null)
                {
                    this.readerWriterLockSlimUniqueHeaderId.Dispose();
                }

                this.readerWriterLockSlimConfiguration = null;
                this.readerWriterLockSlimUniqueHeaderId = null;

                configurationLocal = null;
            }

            this.disposed = true;
        }

        /// <summary>
        /// Gets the host by name local async.
        /// </summary>
        /// <param name="hostName">Name of the host.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The <see cref="Task"/>.</returns>
        private async Task<IPHostEntry> GetHostByNameLocalAsync(string hostName, CancellationToken cancellationToken)
        {
            Contract.Requires(!string.IsNullOrWhiteSpace(hostName));

            return await this.MakeEntryLocalAsync(hostName, cancellationToken);
        }

        /// <summary>
        /// Gets the host entry local async.
        /// </summary>
        /// <param name="hostNameOrAddress">The host name or address.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The <see cref="Task"/>.</returns>
        private async Task<IPHostEntry> GetHostEntryLocalAsync(string hostNameOrAddress, CancellationToken cancellationToken)
        {
            Contract.Requires(!string.IsNullOrEmpty(hostNameOrAddress));

            IPAddress address;
            if (IPAddress.TryParse(hostNameOrAddress, out address))
            {
                var hostEntry = await this.GetHostEntryLocalAsync(address, cancellationToken);

                return hostEntry;
            }

            var entry = await this.MakeEntryLocalAsync(hostNameOrAddress, cancellationToken);

            return entry;
        }

        /// <summary>
        /// Gets the host entry local async.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The <see cref="Task"/>.</returns>
        private async Task<IPHostEntry> GetHostEntryLocalAsync(IPAddress address, CancellationToken cancellationToken)
        {
            Contract.Requires(address != null);

            var response = await this.GetQueryLocalAsync(this.GetArpaFromIp(address), QType.PTR, QClassType.IN, cancellationToken);

            if (response.RecordsPtr.Count > 0)
            {
                var hostEntry = await this.MakeEntryLocalAsync(response.RecordsPtr[0].PtrdName, cancellationToken);

                return hostEntry;
            }

            this.Logger.Warning(
                (int)EventId.NetSendReceiveFail,
                "ResolverDefault.GetHostEntryLocalAsync(): No response.RecordsPtr records found. Returning new 'IPHostEntry()'");

            return new IPHostEntry();
        }

        /// <summary>
        /// Gets the query local async.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="queryType">Type of the query.</param>
        /// <param name="queryClassType">Type of the query class.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The <see cref="Task"/>.</returns>
        private async Task<Response> GetQueryLocalAsync(
            string name, QType queryType, QClassType queryClassType, CancellationToken cancellationToken)
        {
            Contract.Requires(!string.IsNullOrWhiteSpace(name));

            this.InitializeUniqueHeaderId();

            Question question;

            if (queryType == QType.PTR)
            {
                IPAddress tryAddress;
                if (IPAddress.TryParse(name, out tryAddress))
                {
                    var arpaFromIp = this.GetArpaFromIp(tryAddress);
                    question = new Question(arpaFromIp, queryClassType, queryType);
                }
                else
                {
                    question = new Question(name, queryClassType, queryType);
                }
            }
            else
            {
                question = new Question(name, queryClassType, queryType);
            }

            var response = this.SearchInCache(question);

            if (response != null)
            {
                return response;
            }

            var request = new Request();
            request.AddQuestion(question);

            return await this.GetResponseLocalAsync(request, cancellationToken);
        }

        /// <summary>
        /// Gets the response local async.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The <see cref="Task"/>.</returns>
        /// <exception cref="System.InvalidOperationException">Transport type can only be TCP or UDP.</exception>
        private async Task<Response> GetResponseLocalAsync(Request request, CancellationToken cancellationToken)
        {
            Contract.Requires(request != null);

            request.Header.Id = (ushort)this.UniqueHeaderId;
            request.Header.RecursionDesired = this.Configuration.Configuration.UseRecursion;

            if (this.Configuration.Configuration.TransportType == TransportType.Udp)
            {
                var responseUdp =
                    await
                    this.GetUdpRequestLocalAsync(
                        request, 
                        this.Configuration.Configuration.DnsServers.ToArray(), 
                        this.Configuration.Configuration.Retries, 
                        cancellationToken);

                /*If UDP truncation is dtected*/
                if (responseUdp.Header.Truncation)
                {
                    /*Use TCP*/
                    var responseTcp =
                        await
                        this.GetTcpRequestLocalAsync(
                            request,
                            this.Configuration.Configuration.DnsServers.ToArray(),
                            this.Configuration.Configuration.Retries,
                            cancellationToken);

                    return responseTcp;
                }

                return responseUdp;
            }

            if (this.Configuration.Configuration.TransportType == TransportType.Tcp)
            {
                var response =
                    await
                    this.GetTcpRequestLocalAsync(
                        request,
                        this.Configuration.Configuration.DnsServers.ToArray(),
                        this.Configuration.Configuration.Retries,
                        cancellationToken);

                return response;
            }

            throw new InvalidOperationException("Transport type can only be TCP or UDP.");
        }

        /// <summary>
        /// Gets the TCP request local async.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="dnsServers">The DNS servers.</param>
        /// <param name="retries">The retries.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The <see cref="Task"/>.</returns>
        /// <exception cref="System.Net.Sockets.SocketException">Socket exception</exception>
        private async Task<Response> GetTcpRequestLocalAsync(
            Request request, IList<IPEndPoint> dnsServers, int retries, CancellationToken cancellationToken)
        {
            Contract.Requires(request != null);
            Contract.Requires(dnsServers != null);
            Contract.Requires(dnsServers.Count > 0);
            Contract.Requires(retries > 0);

            var currentRetryCount = 0;

            var currentDnsServerIndex = 0;

            while (currentRetryCount < retries)
            {
                var currentDnsServer = dnsServers[currentDnsServerIndex];

                using (var tcpClient = new TcpClient(currentDnsServer.AddressFamily)
                {
                    ReceiveTimeout = this.Configuration.Configuration.DnsTimeout
                })
                {
                    try
                    {
                        await tcpClient.ConnectAsync(currentDnsServer.Address, currentDnsServer.Port);
                    }
                    catch (SocketException exception)
                    {
                        string message;

                        if (exception.SocketErrorCode == SocketError.TimedOut)
                        {
                            message = string.Format(
                                       CultureInfo.InvariantCulture,
                                       ";; Connection to nameserver {0} failed after timeout of {1}ms.",
                                      currentDnsServer,
                                      this.Configuration.Configuration.DnsTimeout);
                            this.Logger.Warning(exception, (int)EventId.NetConnectFail);
                        }
                        else
                        {
                            message = string.Format(
                                CultureInfo.InvariantCulture, ";; Connection to nameserver {0} failed", currentDnsServer);
                            this.Logger.Error(exception, (int)EventId.NetConnectFail);
                        }

                        this.Verbose(message);
                        
                        /*Increment unique header id*/
                        this.UniqueHeaderId++;

                        /*Increment retry count*/
                        Interlocked.Increment(ref currentRetryCount);

                        /*Next attempt*/
                        continue;
                    }
                    
                    using (var bufferedStream = new BufferedStream(tcpClient.GetStream()))
                    {
                        var data = request.Data;

                        var byteArray = data.ToArray();

                        bufferedStream.WriteByte((byte)((byteArray.Length >> 8) & 0xff));
                        bufferedStream.WriteByte((byte)(byteArray.Length & 0xff));

                        try
                        {
                            await bufferedStream.WriteAsync(byteArray, 0, byteArray.Length, cancellationToken);
                        }
                        catch (Exception exception)
                        {
                            var message = string.Format(
                                CultureInfo.InvariantCulture, ";; Data send to nameserver {0} failed. Please consult error logs for more detail.", currentDnsServer);

                            this.Verbose(message);

                            this.Logger.Warning(exception, (int)EventId.NetSendReceiveFail);

                            /*Increment unique header id*/
                            this.UniqueHeaderId++;

                            /*Increment retry count*/
                            Interlocked.Increment(ref currentRetryCount);

                            /*Next attempt*/
                            continue;
                        }
                        
                        try
                        {
                            bufferedStream.Flush();
                        }
                        catch (IOException exception)
                        {
                            var message = string.Format(
                                CultureInfo.InvariantCulture, ";; Data buffer to nameserver {0} failed. Please consult error logs for more detail.", currentDnsServer);

                            this.Verbose(message);

                            this.Logger.Warning(exception, (int)EventId.NetSendReceiveFail);

                            /*Increment unique header id*/
                            this.UniqueHeaderId++;

                            /*Increment retry count*/
                            Interlocked.Increment(ref currentRetryCount);

                            /*Next attempt*/
                            continue;
                        }

                        var transferResponse = new Response();
                        var soa = 0;
                        var messageSize = 0;

                        while (true)
                        {
                            var length = bufferedStream.ReadByte() << 8 | bufferedStream.ReadByte();

                            /*Connection to DNS server failed*/
                            if (length <= 0)
                            {
                                var msg = string.Format(
                                    CultureInfo.InvariantCulture,
                                    ";; Connection to nameserver {0} failed",
                                    currentDnsServer);

                                this.Logger.Warning((int)EventId.NetConnectFail, msg);

                                this.Verbose(msg);

                                /*Next DNS server*/
                                Interlocked.Increment(ref currentDnsServerIndex);

                                /*Reset retry count*/
                                Interlocked.Exchange(ref currentRetryCount, 0);

                                /*Increment unique header id*/
                                this.UniqueHeaderId++;

                                /*Continue with next DNS server*/
                                continue;
                            }

                            messageSize += length;

                            byteArray = new byte[length];

                            try
                            {
                                await bufferedStream.ReadAsync(byteArray, 0, length, cancellationToken);
                            }
                            catch (Exception exception)
                            {
                                var msg = string.Format(
                                    CultureInfo.InvariantCulture,
                                    ";; Data read on nameserver {0} failed. Please see error logs for more details.",
                                    currentDnsServer);

                                this.Verbose(msg);

                                this.Logger.Warning(exception, (int)EventId.NetSendReceiveFail);

                                /*Reset retry count*/
                                Interlocked.Exchange(ref currentRetryCount, 0);

                                /*Increment unique header id*/
                                this.UniqueHeaderId++;

                                continue; /*Continue with next DNS server*/
                            }
                            
                            var response = new Response(this.RecordReaderFactory, currentDnsServer, byteArray, length);

                            if (response.Answers.Count == 0)
                            {
                                throw new SocketException((int)SocketError.HostNotFound);
                            }

                            if (response.Header.ResponseCode != RCodeType.NoError)
                            {
                                return response;
                            }

                            if (response.Questions[0].QType != QType.AXFR)
                            {
                                this.AddToCache(response);
                                return response;
                            }

                            // Zone transfer
                            if (transferResponse.Questions.Count == 0)
                            {
                                foreach (Question question in response.Questions)
                                {
                                    transferResponse.Questions.Add(question);
                                }
                            }

                            foreach (AnswerResourceRecord answer in response.Answers)
                            {
                                transferResponse.Answers.Add(answer);
                            }

                            foreach (AuthorityResourceRecord authority in response.Authorities)
                            {
                                transferResponse.Authorities.Add(authority);
                            }

                            foreach (AdditionalResourceRecord additional in response.Additionals)
                            {
                                transferResponse.Additionals.Add(additional);
                            }

                            if (response.Answers[0].RRType == RecordType.SOA)
                            {
                                Interlocked.Increment(ref soa);
                            }

                            if (soa == 2)
                            {
                                transferResponse.Header.QdCount = (ushort)transferResponse.Questions.Count;
                                transferResponse.Header.Ancount = (ushort)transferResponse.Answers.Count;
                                transferResponse.Header.Nscount = (ushort)transferResponse.Authorities.Count;
                                transferResponse.Header.Arcount = (ushort)transferResponse.Additionals.Count;
                                transferResponse.MessageSize = messageSize;

                                return transferResponse;
                            }

                            /*Increment unique header id*/
                            this.UniqueHeaderId++;
                        }
                    }
                }
            }

            var responseMaxRetriesExceeded = new Response { Error = MaxRetriesExceededMessage };
            return responseMaxRetriesExceeded;
        }
        
        /// <summary>
        /// Gets the UDP request local async.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="dnsServers">The DNS servers.</param>
        /// <param name="retries">The retries.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The <see cref="Task"/>.</returns>
        /// <exception cref="System.Net.Sockets.SocketException">
        /// Socket exception.
        /// </exception>
        private async Task<Response> GetUdpRequestLocalAsync(
            Request request, IList<IPEndPoint> dnsServers, int retries, CancellationToken cancellationToken)
        {
            Contract.Requires(request != null);
            Contract.Requires(dnsServers != null);
            Contract.Requires(dnsServers.Count > 0);
            Contract.Requires(retries > 0);

            var responseMessage = new byte[MaxSizeUdpDatagram];

            var currentRetryCount = 0;

            var currentDnsServerIndex = 0;

            while (currentRetryCount < retries)
            {
                var currentDnsServer = dnsServers[currentDnsServerIndex];

                using (var socket = new Socket(currentDnsServer.AddressFamily, SocketType.Dgram, ProtocolType.Udp))
                {
                    socket.SetSocketOption(
                        SocketOptionLevel.Socket,
                        SocketOptionName.ReceiveTimeout,
                        this.Configuration.Configuration.DnsTimeout);

                    var array = request.Data.ToArray();

                    try
                    {
                        using (var args = new SocketAsyncEventArgs())
                        {
                            args.RemoteEndPoint = currentDnsServer;
                            args.SetBuffer(array, 0, array.Length);
                            var socketAwaitable = new SocketAwaitable(args);

                            await socket.SendToAsync(socketAwaitable);
                        }
                    }
                    catch (SocketException exception)
                    {
                        string message;

                        if (exception.SocketErrorCode == SocketError.TimedOut)
                        {
                            message = string.Format(
                                CultureInfo.InvariantCulture,
                                ";; Connection to nameserver {0} failed after timeout of {1}ms.",
                                currentDnsServer,
                                this.Configuration.Configuration.DnsTimeout);
                            
                            this.Logger.Warning(exception, (int)EventId.NetSendReceiveFail);
                        }
                        else
                        {
                            message = string.Format(
                                CultureInfo.InvariantCulture, 
                                ";; Connection to nameserver {0} failed. SocketError = '{1}'.", 
                                currentDnsServer, 
                                exception.SocketErrorCode);

                            this.Logger.Error(exception, (int)EventId.NetSendReceiveFail);
                        }

                        this.Verbose(message);
                        
                        /*Increment unique header id*/
                        this.UniqueHeaderId++;

                        /*Increment retry count*/
                        Interlocked.Increment(ref currentRetryCount);

                        /*Next attempt*/
                        continue;
                    }
                    catch (Exception exception)
                    {
                        var message = string.Format(
                                CultureInfo.InvariantCulture, ";; Connection to nameserver {0} failed", currentDnsServer);

                        this.Verbose(message);

                        this.Logger.Warning(exception, (int)EventId.NetSendReceiveFail);

                        /*Increment retry count*/
                        Interlocked.Increment(ref currentRetryCount);

                        /*Increment unique header id*/
                        this.UniqueHeaderId++;

                        /*Next attempt*/
                        continue;
                    }
                    
                    var socketError = SocketError.Success;

                    int received;

                    try
                    {

                        using (var args = new SocketAsyncEventArgs())
                        {
                            args.RemoteEndPoint = currentDnsServer;
                            args.SetBuffer(responseMessage, 0, MaxSizeUdpDatagram);
                            var socketAwaitable = new SocketAwaitable(args);
                            
                            await socket.ReceiveFromAsync(socketAwaitable);
                            var bytesTransferred = args.BytesTransferred;
                            received = bytesTransferred;

                            socketError = args.SocketError;
                            if (socketError != SocketError.Success)
                            {
                                throw new SocketException((int)socketError);
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        var msg = string.Format(
                                    CultureInfo.InvariantCulture,
                                    ";; Data read on nameserver {0} failed with exception code '{1}'. Please see error logs for more details.",
                                    currentDnsServer,
                                    socketError);

                        this.Verbose(msg);

                        this.Logger.Warning(exception, (int)EventId.NetSendReceiveFail);

                        /*Increment unique header id*/
                        this.UniqueHeaderId++;

                        /*Next DNS server*/
                        Interlocked.Increment(ref currentDnsServerIndex);

                        /*Reset retry count*/
                        Interlocked.Exchange(ref currentRetryCount, 0);

                        /*Next attempt*/
                        continue;
                    }
                    
                    var data = new byte[received];

                    Array.Copy(responseMessage, data, received);

                    var response = new Response(this.RecordReaderFactory, currentDnsServer, data, received);

                    if (response.Header.Truncation)
                    {
                        return response;
                    }

                    if (response.Answers.Count == 0)
                    {
                        throw new SocketException((int)SocketError.HostNotFound);
                    }
                    
                    this.AddToCache(response);
                    
                    return response;
                }
            }

            var responseMaxRetriesExceeded = new Response { Error = MaxRetriesExceededMessage };
            return responseMaxRetriesExceeded;
        }

        /// <summary>
        /// Makes the entry local async.
        /// </summary>
        /// <param name="hostName">Name of the host.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The <see cref="Task"/>.</returns>
        private async Task<IPHostEntry> MakeEntryLocalAsync(string hostName, CancellationToken cancellationToken)
        {
            Contract.Requires(!string.IsNullOrWhiteSpace(hostName));

            this.InitializeUniqueHeaderId();

            var entry = new IPHostEntry { HostName = hostName };

            Response responsesInterNetwork;

            Response responsesInterNetworkV6 = null;

            try
            {
                 responsesInterNetwork = await this.QueryAsync(hostName, QType.A, cancellationToken);
            }
            catch (Exception exception)
            {
                this.Logger.Error(exception, (int)EventId.NetSendReceiveFail, true);
                throw;
            }

            try
            {
                responsesInterNetworkV6 = await this.QueryAsync(hostName, QType.AAAA, cancellationToken);
            }
            catch (AggregateException aggregateException)
            {
                ReadOnlyCollection<Exception> exceptions = aggregateException.InnerExceptions;

                foreach (var exception in exceptions)
                {
                    if (exception.GetType() == typeof(SocketException))
                    {
                        var socketException = (SocketException)exception;

                        if (socketException.SocketErrorCode == SocketError.HostNotFound)
                        {
                            /*Don't care*/
                        }
                        else
                        {
                            this.Logger.Error(socketException, (int)EventId.NetSendReceiveFail);
                        }
                    }
                }
            }
            catch (SocketException socketException)
            {
                if (socketException.SocketErrorCode == SocketError.HostNotFound)
                {
                    /*Don't care*/
                }
                else
                {
                    this.Logger.Error(socketException, (int)EventId.NetSendReceiveFail);
                }
            }
            catch (Exception exception)
            {
                this.Logger.Error(exception, (int)EventId.NetSendReceiveFail);
            }

            // fill AddressList and aliases
            var addressList = new List<IPAddress>();
            var aliases = new List<string>();

            /*V4 Addresses*/
            foreach (AnswerResourceRecord answerrr in responsesInterNetwork.Answers)
            {
                if (answerrr.RRType == RecordType.A)
                {
                    addressList.Add(IPAddress.Parse(answerrr.Record.ToString()));
                    entry.HostName = answerrr.Name;
                }
                else
                {
                    if (answerrr.RRType == RecordType.CNAME)
                    {
                        aliases.Add(answerrr.Name);
                    }
                }
            }

            /*V6 Addresses*/
            if (responsesInterNetworkV6 != null)
            {
                foreach (AnswerResourceRecord answerrr in responsesInterNetworkV6.Answers)
                {
                    if (answerrr.RRType == RecordType.AAAA)
                    {
                        addressList.Add(IPAddress.Parse(answerrr.Record.ToString()));
                        entry.HostName = answerrr.Name;
                    }
                    else
                    {
                        if (answerrr.RRType == RecordType.CNAME)
                        {
                            aliases.Add(answerrr.Name);
                        }
                    }
                }
            }

            entry.AddressList = addressList.ToArray();
            entry.Aliases = aliases.ToArray();

            return entry;
        }

        /// <summary>
        /// Resolves the local async.
        /// </summary>
        /// <param name="hostName">Name of the host.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The <see cref="Task"/>.</returns>
        private async Task<IPHostEntry> ResolveLocalAsync(string hostName, CancellationToken cancellationToken)
        {
            Contract.Requires(!string.IsNullOrWhiteSpace(hostName));

            return await this.MakeEntryLocalAsync(hostName, cancellationToken);
        }

        #endregion
    }
}